package com.deuwise.common.aspect;


import com.deuwise.common.exception.RException;
import com.deuwise.common.exception.handler.ApplicationExceptionHandler;
import com.deuwise.common.utils.IPUtils;
import com.deuwise.common.utils.JSONUtils;

import com.deuwise.common.xss.SQLFilter;
import com.deuwise.config.oauth2.JWTUtil;
import com.deuwise.system.entity.LogDO;
import com.deuwise.system.entity.SysUser;
import com.deuwise.system.entity.from.SysLoginForm;
import com.deuwise.system.service.SysUserService;
import com.deuwise.system.service.LogDoService;

import com.google.gson.Gson;
import io.swagger.annotations.ApiOperation;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.servlet.ShiroHttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


/**
 * @author chenwenchao
 * @date 2018-07
 */
@Component
@Aspect
public class LogAspect {

    private Logger logger = LoggerFactory.getLogger(LogAspect.class);


    @Autowired
    LogDoService logDoService;

    @Autowired
    private SysUserService sysUserService;

    // 存在SQL注入风险
    private static final String IS_SQL_INJECTION = "输入参数存在SQL注入风险";

    private static final String UNVALIDATED_INPUT = "输入参数含有非法字符";

    private static final String ERORR_INPUT = "输入的参数非法";

    @Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
    public void logPointCut() {

    }

    @Autowired
    private ApplicationExceptionHandler applicationExceptionHandler;

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = null;
        //访问目标方法的参数：
        Object[] args = point.getArgs();
        //sql注入过滤操作
        for (Object arg : args) {
            if (point.toString().equals("execution(R com.deuwise.system.controller.SysMenuController.update(SysMenu))")) {
                continue;
            }
            if (arg == null) {
                continue;
            }
            String str = String.valueOf(arg);
            if (StringUtils.isBlank(str)) {
                continue;
            }
            if (!SQLFilter.sqlStrFilter(str)) {
                // logger.info(IS_SQL_INJECTION);
                throw new RException(IS_SQL_INJECTION);
            }
          /*  if (!SQLFilter.isIllegalStr(str)) {
               // logger.info(UNVALIDATED_INPUT);
                throw new RException(UNVALIDATED_INPUT);
            }*/
        }
        if (args != null && args.length > 0 && args[0] != null && args[0].getClass() == String.class) {
            args[0] = "";
        }
        if (result == null) {
            result = point.proceed();
            long time = System.currentTimeMillis() - beginTime;
            saveLog(point, null, time, false, null);
        }
        //用改变后的参数执行目标方法
        /*Object returnValue = point.proceed(args);*/

        return result;
    }


    /**
     * 记录异常
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void afterException(JoinPoint joinPoint, Exception e) throws InterruptedException, ClassNotFoundException {
        if (e != null && e.getMessage() != null) {
            if (e.getMessage().startsWith("440") || "44000".equals(e.getMessage()) || "406".equals(e.getMessage())) {
                saveLog(null, joinPoint, 0, false, "");
            } else {
                System.out.print("-----------afterException:" + e.getMessage());
                //添加日志
                saveLog(null, joinPoint, 0, true, e.getMessage());
            }
        } else {
            System.out.print("-----------afterException:" + e.getMessage());
            //添加日志
            saveLog(null, joinPoint, 0, true, e.getMessage());
        }
    }

    private void saveLog(ProceedingJoinPoint point, JoinPoint joinPoint, long time, boolean isEx, String expectionInfo) throws InterruptedException, ClassNotFoundException {

        LogDO log = new LogDO();
        log.setTime((int) time);
        if (isEx) {
            log.setStatus(0);
            expectionInfo = JSONUtils.beanToJson(expectionInfo);
            if (expectionInfo.length() > 1010) {
                expectionInfo = expectionInfo.replace(" ", "").trim();
                Integer length = expectionInfo.length();
                length = expectionInfo.length() > 1010 ? 1010 : length;
                expectionInfo = expectionInfo.substring(0, length);
            }
            log.setException(expectionInfo);
        } else {
            log.setStatus(1);
        }
        MethodSignature signature = null;
        if (point == null) {
            signature = (MethodSignature) joinPoint.getSignature();
        } else {
            signature = (MethodSignature) point.getSignature();
        }

        Method method = signature.getMethod();

        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        if (apiOperation != null) {
            //注解里描述的功能说明
            log.setOperation(apiOperation.value());
        }
        boolean isLogin = false;
        String className = "";
        Object[] args = null;
        //请求的方法名
        if (point != null) {
            className = point.getTarget().getClass().getName();
            //请求的参数
            args = point.getArgs();
        } else {
            className = joinPoint.getTarget().getClass().getName();
            args = joinPoint.getArgs();
        }
        String methodName = signature.getName();
        String methodInfo = String.format("%s.%s()", className, methodName);
        log.setMethod(methodInfo);
        if ("com.deuwise.system.controller.SysLoginController.login()".equals(methodInfo)) {
            isLogin = true;
            SysLoginForm loginForm = (SysLoginForm) args[0];

            SysUser user = sysUserService.queryByUserName(loginForm.getUsername());
            log.setUserName(loginForm.getUsername());
            if (user != null) {
                log.setUserId(user.getId());
            }
            log.setStatus(2);
        }
        //因为导入参数特殊，所以过滤掉 新增用户
        // if (!log.getOperation().contains("导入") && !log.getOperation().contains("新增服务") && !log.getOperation().contains("新增用户")) {
        //请求的方法名
        log.setMethod(className + "." + methodName + "()");

        String clazzName =className;
        //获取参数名称和值
        try {
            Map<String, Object> nameAndArgs = this.getFieldsName(this.getClass(), clazzName, methodName, args);
            System.out.println(nameAndArgs);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            if (args != null && args.length > 0) {
                if (!(args[0] instanceof ShiroHttpServletRequest)) {
                    if(!(args[0] instanceof ShiroHttpServletResponse)) {
                        String params = new Gson().toJson(args[0]);
                        log.setParams(params);
                    }

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        //获取request
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //一些系统监控
        if (requestAttributes != null) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ip = IPUtils.getIpAddr(request);
            //服务器地址
            String url = "http://" + request.getServerName()
                    + ":"      //端口号
                    + request.getServerPort() + request.getRequestURI();
            log.setUrl(url);
            log.setIp(ip);
            String authorization = (String) SecurityUtils.getSubject().getPrincipal();
            String token = request.getParameter("token");
            if (StringUtils.isEmpty(authorization)) {
                authorization = token;
            }
            if (!StringUtils.isEmpty(authorization)) {
                String userName = JWTUtil.getUserId(authorization);
                SysUser user = sysUserService.selectById(userName);
                if (user != null) {
                    log.setUserId(user.getId());
                    log.setUserName(user.getUsername());
                }
            }

        }

        //用户名

        //系统当前时间
        Date date = new Date();
        log.setCreateTime(date);
        try {
            //保存日志
            logDoService.insert(log);
        } catch (RException ex) {
            logger.error("数据库连接出错" + ex.getMessage());
        }
    }


    /**
     * @return Map<String , Object>
     * @Description 获取字段名和字段值
     * @Author 刘俊重
     * @date 2017年7月6日 
     */
    private Map<String, Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);
        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            // exception    
        }
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < cm.getParameterTypes().length; i++) {
            map.put(attr.variableName(i + pos), args[i]);//paramNames即参数名    
        }
        return map;
    }
}
